using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration.Provider;

namespace NSecurity
{
    public abstract class AccessControlProviderBase<TSecurable, TVerb, TSubject> : ProviderBase
        where TSecurable : class
        where TVerb : class
        where TSubject : class
    {
        public virtual string ApplicationName { get; set; }

        /// <summary>
        /// Initializes the provider.
        /// </summary>
        /// <param name="name">The friendly name of the provider.</param>
        /// <param name="config">A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider.</param>
        /// <exception cref="System.ArgumentNullException">config</exception>
        /// <exception cref="System.Security.SecurityException">Unable to initialize access control list.</exception>
        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null)
            {
                throw new ArgumentNullException("config");
            }

            if (string.IsNullOrEmpty(name))
            {
                name = this.GetType().Name;
            }

            base.Initialize(name, config);
            this.ApplicationName = this.GetValue<string>(config, "applicationName", x => string.IsNullOrEmpty(x) ? "/" : null);
            this.InitializeAccessControlEntries();
        }

        /// <summary>
        /// Adds the access control entry.
        /// </summary>
        /// <param name="securable">The securable.</param>
        /// <param name="verb">The verb.</param>
        /// <param name="subject">The subject.</param>
        /// <param name="accessType">Type of the access.</param>
        /// <returns></returns>
        //protected internal abstract AccessControlEntry<TSecurable, TVerb, TSubject> AddAccessControlEntry(TSecurable securable, TVerb verb, TSubject subject, AccessType accessType);
        protected internal abstract AccessControlEntry AddAccessControlEntry(TSecurable securable, TVerb verb, TSubject subject, AccessType accessType);

        /// <summary>
        /// Clears the access control entries.
        /// </summary>
        protected internal abstract void ClearAccessControlEntries();

        /// <summary>
        /// Finds the access control entry.
        /// </summary>
        /// <param name="securable">The securable.</param>
        /// <param name="verb">The verb.</param>
        /// <param name="subject">The subject.</param>
        /// <returns></returns>
        //protected internal abstract IEnumerable<AccessControlEntry<TSecurable, TVerb, TSubject>> FindAllAccessControlEntries(TSecurable securable
        //                                , TVerb verb
        //                                , IEnumerable<TSubject> subject);
        protected internal abstract IEnumerable<AccessControlEntry> FindAllAccessControlEntries(TSecurable securable
                                        , TVerb verb
                                        , IEnumerable<TSubject> subject);

        /// <summary>
        /// Checks for extra attributes. Throws an exception if unrecognized attributes remain
        /// </summary>
        /// <param name="config">The config.</param>
        /// <exception cref="System.Configuration.Provider.ProviderException">Unrecognized attribute:  + attr</exception>
        protected void CheckForExtraAttributes(NameValueCollection config)
        {
            if (config.Count > 0)
            {
                var attr = config.GetKey(0);
                if (string.IsNullOrEmpty(attr) == false)
                {
                    throw new ProviderException("Unrecognized attribute: " + attr);
                }
            }
        }

        /// <summary>
        /// Gets the value of a config property.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="config">The config.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="setUp">The set up.</param>
        /// <returns></returns>
        protected T GetValue<T>(NameValueCollection config, string propertyName, Func<string, string> setUp = null)
        {
            var value = config[propertyName];
            config.Remove(propertyName);
            value = setUp != null ? setUp(value) : value;
            var result = (T)Convert.ChangeType(value, typeof(T));
            return result;
        }

        /// <summary>
        /// Initializes the access control entries.
        /// </summary>
        protected abstract void InitializeAccessControlEntries();

        /// <summary>
        /// Persists the Access Control Entries.
        /// </summary>
        public abstract void PersistChanges();

        /// <summary>
        /// Loads the access control.
        /// </summary>
        /// <param name="entries">The entries.</param>
        public abstract void LoadAccessControl(string entries = null);
    }
}